package com.huihe.framework.service;

import cn.hutool.core.util.StrUtil;
import com.huihe.common.constant.Constants;
import com.huihe.common.constant.RedisConstants;
import com.huihe.common.utils.*;
import com.huihe.storage.dto.LoginUser;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Token 相关的 Service层
 * 该类主要作用是设置那些信息用来生成字段,并且保存和Token关联的字段到Redis中
 * 日志格式:
 *      [TOSE]:Token Service
 *      [GEIP]:Get IP
 *      [GEBR]:Get Browser
 *      [GEOS]:Get OS
 *      [CHTO]:Check Token
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenService {

    /**
     * 令牌有效时间(分)
     */
    @Value(value = "${user.token.expireTime}")
    private int expireTime;

    /**
     * Token表示
     */
    @Value(value = "${user.token.header}")
    private String header;

    /**
     * 1秒等于1000毫秒
     */
    protected static final long MILLIS_SECOND = 1000;

    /**
     * 1分等于60秒
     */
    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    /**
     * 20分钟时间
     */
    private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L;

    /**
     * Redis工具类
     */
    private final RedisUtils redisUtils;

    /**
     * 创建登录用户的 Token , Token格式如下
     * Header:
     * {
     *     "alg":"ES512",
     *     "typ":"JWT"
     * }
     * Payload:
     * {
     *     "id":"uuid"
     * }
     * 注 :该 uuid 不是表中的 id ,而是 Redis 中的键 , 这样设计的目的是为了保护用户的信息安全 , 不在 token 中携带用户信息(真正的用户信息都是在 Redis 键对应的 值中)
     * @param loginUser 验证正确的用户部分信息(用户名和密码)
     * @return 生成的 Token
     */
    public String createToken(LoginUser loginUser){
        // 生成 Redis 的 UUID 键
        String redisUUID = UUID.randomUUID().toString();
        // 将 ReidsKey 保存到登录用户信息对象中
        loginUser.setRedisUUID(redisUUID);
        //生成更多的信息
        setUserAgent(loginUser);
        // 将用户信息存入 Redis 中
        refreshToken(loginUser);
        // 将 ReidsKey 存入 Map 中 , 作为生成 Token 的信息
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, redisUUID);
        claims.put(Constants.JWT_USERNAME,loginUser.getSysUser().getAccount());
        // 生成 Token
        return TokenUtils.createToken(claims);
    }

    /**
     * 根据请求中的Token获取Redis中的用户身份信息
     * @return 用户信息
     */
    public LoginUser getLoginUserByToken(HttpServletRequest request) {
        // 获取HTTP请求携带的令牌
        String token = getToken(request);
        if (StrUtil.isNotEmpty(token)) {
            try {
                Claims claims = TokenUtils.parseToken(token);
                // 解析对应的权限以及用户信息
                String redisUuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getRedisKey(redisUuid);
                if (redisUtils.hasKey(userKey)){
                    return redisUtils.getCacheObject(userKey);
                }
                else
                    return null;
            } catch (Exception e) {
                log.error("[TOSE][CHTO] 获取用户信息异常:" + e.getMessage(),e);
            }
        }
        return null;
    }

    /**
     * 设置用户代理信息
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser) {
        //解析User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("SysUser-Agent"));
        //获得IP
        String ip = IpUtils.getIpAddr();
        //根据IP获得更多内容
        if (ip != null){
            log.info("[TOSE][GEIP] 用户:{}本次请求的IP为:{}",loginUser.getSysUser().getAccount(),ip);
            loginUser.setIpaddr(ip);
            loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
            log.info("[TOSE][GEIP] 用户:{}本次请求的地址为:{}",loginUser.getSysUser().getAccount(),loginUser.getLoginLocation());
        }
        //获得客户端浏览器类型
        loginUser.setBrowser(userAgent.getBrowser().getName());
        log.info("[TOSE][GEBR] 用户:{}本次请求的浏览器为:{}",loginUser.getSysUser().getAccount(),loginUser.getBrowser());
        //获得客户端操作系统
        loginUser.setOs(userAgent.getOperatingSystem().getName());
        log.info("[TOSE][GEOS] 用户:{}本次请求的操作系统:{}",loginUser.getSysUser().getAccount(),loginUser.getOs());
    }

    /**
     * 刷新令牌有效期(Redis中)
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getRedisKey(loginUser.getRedisUUID());
        redisUtils.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

    /**
     * 删除Redis中的用户身份信息
     */
    public void delLoginUser(String token) {
        if (StrUtil.isNotEmpty(token)) {
            String userKey = getRedisKey(token);
            redisUtils.deleteObject(userKey);
        }
    }

    /**
     * 创建Token存储在Redis的键名
     * @param uuid 为key生成的UUID
     * @return key
     */
    private String getRedisKey(String uuid) {
        return RedisConstants.LOGIN_TOKEN_KEY + uuid;
    }

    /**
     * 获取请求token
     *
     * @param request 携带Token的请求
     * @return token
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StrUtil.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            //移除前缀
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    /**
     * 验证令牌有效期，相差 不足 20分钟，自动刷新缓存
     * @param loginUser 登录信息
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) {
            refreshToken(loginUser);
        }
    }
}
